using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using SourFruit.player;
using SourFruit.level;
using SourFruit.level.obj;
using System.Diagnostics;
using System.IO;
using System.Xml.Serialization;
using SourFruit.menu;

namespace SourFruit
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        private enum Controller {keyboard, joystick, phone }
        private enum LevelType { SpaceLevel, FireLevel}
        Controller controller;
        GraphicsDeviceManager graphics;
        Texture2D asteroidTex, goalTex, collectableTex;
        Texture2D[] starTex;
        SpriteBatch spriteBatch;
        Graphics graph;
        Player player;
        Input input;
        Physics physics;
        Level level;
        MainMenuWindow menu;
        Boolean menuOpen, gameEnded;
        private Texture2D playerText;
        private SpriteFont menufont;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            graphics.PreferredBackBufferWidth = GameConfig.Instance.screenWidth;
            graphics.PreferredBackBufferHeight = GameConfig.Instance.screenHeight;            
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here            
            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);             
            playerText = Content.Load<Texture2D>("Player");          
            collectableTex = Content.Load<Texture2D>("collectable");
            asteroidTex = Content.Load<Texture2D>("asteroid2");
            starTex = new Texture2D[4] { Content.Load<Texture2D>("fireball_1"), Content.Load<Texture2D>("fireball_2"), Content.Load<Texture2D>("fireball_3"), Content.Load<Texture2D>("fireball_4") };
            goalTex = Content.Load<Texture2D>("goal");
            menufont = Content.Load<SpriteFont>("menufont");
            NewGame();
            // TODO: use this.Content to load your game content here
        }

        private Level CreateLevel(LevelType Type)
        {
            LevelFactory factory = null;
            Goal goal;            
            List <Platform> platforms = new List<Platform>();
            List<Collectable> collectables = new List<Collectable>();
            switch (Type)
            {
                case LevelType.SpaceLevel:
                    factory = new SpaceLevel();
                    goal = new Goal(GameConfig.Instance.screenWidth *4, GameConfig.Instance.screenHeight / 4, goalTex.Width, goalTex.Height, goalTex);
                    for (int i = 0; i < 1000; i++)
                    {
                        platforms.Add(factory.CreatePlatform((int)SpaceLevel.SpacePlatforms.small_asteroid, i * 100, 400, asteroidTex, MovingOptions.upDown));
                    }
                    
                    for (int i = 0; i < 1000; i++)
                    {
                        platforms.Add(factory.CreatePlatform((int)SpaceLevel.SpacePlatforms.tiny_sun, i * 40, 700, starTex, MovingOptions.still));
                    }
                    for (int i = 0; i < 1000; i++)
                    {
                        collectables.Add(factory.CreateCollectable(1, i * 20, 220, collectableTex));
                    }
                    break;
                default:
                    factory = new SpaceLevel();
                    goal = new Goal(GameConfig.Instance.screenWidth * 4, GameConfig.Instance.screenHeight / 4, goalTex.Width, goalTex.Height, goalTex);
                    break;
            }            

            Level level = new Level(goal);
            foreach (Platform p in platforms)
                level.addPlatform(p);
            foreach (Collectable c in collectables)
                level.addCollectable(c);

            return level;
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        private void performAction(InputActions?[] action, GameTime gametime)
        {
            if (!gameEnded && action[0] == InputActions.escape)
            {
                menuOpen = !menuOpen;
                menu.CreateEventMenuChanged(menuOpen);
            }
            else if (!menuOpen)
            {
                if (action[0] == InputActions.left)
                    physics.MovePlayerX(false, gametime);
                else if (action[0] == InputActions.right)
                    physics.MovePlayerX(true, gametime);
                if (action[1] == InputActions.jump)
                    physics.Jump();
                physics.UpdateJump(gametime);
                physics.Fall(gametime);
            }
            else if (action[0] == InputActions.up)
                menu.GoUP();
            else if (action[0] == InputActions.down)
                menu.GoDOWN();
            else if (action[0] == InputActions.confirm)
                menuOpen = menu.Select();
            else
                menu.CreateEventMenuChanged(menuOpen);
        }

        /// <summary>
        /// Runs the game logic.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            //input.Update(menu,physics, gameTime, openMenu);
            if (player.Position.Y + 5 > GameConfig.Instance.screenHeight)
                GameEnded();            
            if (level != null && !menuOpen)
            {
                foreach (var p in level.Platforms)
                {
                    p.Value.CreateEventUpdateImage(gameTime);
                    if (p.Value.PosX > GameConfig.Instance.screenWidth + 100 || p.Value.PosX < 0 - 100)
                        continue;
                    p.Value.MoveObject(gameTime);
                }
            }
            performAction(input.Update(gameTime, gameEnded), gameTime);
            base.Update(gameTime);
        }

        private void Config()
        {

        }

        public void QuitProgram()
        {
            saveKeyConfig(input, "keyconfig");
            Exit();
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            if (menuOpen) GraphicsDevice.Clear(Color.Black);
            else GraphicsDevice.Clear(Color.Black);
            spriteBatch.Begin();
            graph.Draw(spriteBatch, menufont);
            base.Draw(gameTime);
            spriteBatch.End();
        }

        public void SetMenu(Boolean _menuOpen, String[] _textures, int _highLighted)
        {
            menuOpen = _menuOpen;
        }

        private void ChooseLevel(int id)
        {
            player = new Player(new Vector2(40f, 20f), Vector2.Zero, 10, 10, playerText);
            graph = new Graphics(player.getPlayerTexture(), new String[3] { "New Game", "Config", "Quit" });
            menu = new MainMenuWindow("New Game", "Config", "Quit", "Key Config", "Sound Config", "Back");
            level = CreateLevel(LevelType.SpaceLevel);
            menuOpen = true;
            gameEnded = false;
            menu.LoadLevelEvent += ChooseLevel;
            menu.MenuChanged += graph.SetMenu;
            menu.MenuChanged += this.SetMenu;
            menu.QuitCalled += this.QuitProgram;
            menu.ChangeGameInput += input.bindToKey;            
            player.PositionChanged += graph.PlayerMoved;
            player.PlayerDiedEvent += this.GameEnded;
            level.Goal.GoalReachedEvent += this.GameEnded;
            graph.addObject(level.Goal.PosX, level.Goal.PosY, level.Goal.Width, level.Goal.Height, level.Goal.ID, level.Goal.Texture);
            level.Goal.PositionChanged += graph.ObjectMoved;
            foreach (var p in level.Platforms)
            {
                Platform platform = p.Value;
                graph.addObject(platform.PosX, platform.PosY, platform.Width, platform.Height, platform.ID, platform.Texture);
                platform.PositionChanged += graph.ObjectMoved;
                platform.UpdateImageEvent += graph.ObjectTextureChanged;
            }
            foreach (var c in level.Collectables)
            {
                Collectable collectable = c.Value;
                graph.addObject(collectable.PosX, collectable.PosY, collectable.Width, collectable.Height, collectable.ID, collectable.Texture);
                collectable.PositionChanged += graph.ObjectMoved;
                collectable.ItemCollected += graph.RemoveObject;
                collectable.ItemCollected += level.removeCollectable;
            }
            physics = new Physics(level, player);            
        }

        private void NewGame()
        {
           player = new Player(new Vector2(-100f, -100f), Vector2.Zero, 10, 10, playerText);
           graph = new Graphics(player.getPlayerTexture(), new String[3] {"New Game", "Config", "Quit"});
           menu = new MainMenuWindow("New Game", "Config", "Quit", "Key Config", "Sound Config", "Back");
           if (input != null)
               saveKeyConfig(input, "keyconfig");
           input = loadKeyConfig("keyconfig", Controller.keyboard);
           menuOpen = true;
           gameEnded = true;           
           menu.LoadLevelEvent += ChooseLevel;
           menu.MenuChanged += graph.SetMenu;
           menu.MenuChanged += this.SetMenu;
           menu.QuitCalled += this.QuitProgram;
           menu.ChangeGameInput += input.bindToKey;
           menu.CreateEventMenuChanged(true);
        }

        private void GameEnded()
        {
            NewGame();
        }

        private void saveKeyConfig(Input data, string filename)
        {
            // Open the file, creating it if necessary
            FileStream stream = File.Open(filename, FileMode.OpenOrCreate);
            try
            {
                // Convert the object to XML data and put it in the stream
                XmlSerializer serializer = new XmlSerializer(data.GetType());
                serializer.Serialize(stream, data);
            }
            finally
            {
                // Close the file
                stream.Close();
            }
        }

        private Input loadKeyConfig(string filename, Controller controller)
        {
            Input data;
            switch (controller)
            {
                default:
                    data = new KeyboardInput(Keys.W, Keys.S, Keys.A, Keys.D, Keys.Space, Keys.Enter, Keys.Back);
                    break;
            }

            // Open the file
            FileStream stream = File.Open(filename, FileMode.OpenOrCreate,
            FileAccess.Read);

            if (stream.Length > 0)
            {
                try
                {
                    // Read the data from the file
                   XmlSerializer serializer = new XmlSerializer(data.GetType());
                   data = (Input)serializer.Deserialize(stream);
                }
                finally
                {
                    // Close the file
                    stream.Close();
                }
            }
            return data;
        }
    }
}
